﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace Noris.Tools.TraceVisualiser.BlockingVisualiser.Data
{
    #region class SqlLockData : data o jednom deadlocku
    /// <summary>
    /// SqlLockData : data o jednom deadlocku
    /// </summary>
    internal class SqlLockData
	{
        #region Konstrukce a načtení z XML
        private SqlLockData(TraceData.EventInfo eventInfo)
		{
            this.EventInfo = eventInfo;
            this.SpIds = new Dictionary<string, SqlSpidInfo>();
            this.Processes = new Dictionary<string, SqlProcessInfo>();
            this.Resources = new Dictionary<string, SqlResourceInfo>();
		}
        /// <summary>
        /// Data o události
        /// </summary>
        public TraceData.EventInfo EventInfo { get; private set; }
        /// <summary>
        /// Reference na TraceData, pro získávání dalších informací o procesech
        /// </summary>
        public TraceData.MainInfo TraceData { get { return this.EventInfo.Main; } }
        /// <summary>
        /// Vrátí objekt s daty deadlocku
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public static SqlLockData CreateFromXml(TraceData.EventInfo eventInfo, string text)
		{
            return CreateFromXml(eventInfo, text, (DateTime?)null);
		}
        /// <summary>
        /// Vrátí objekt s daty deadlocku
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public static SqlLockData CreateFromXml(TraceData.EventInfo eventInfo, string text, DateTime? deadlockTime)
		{
			System.Xml.Linq.XDocument xDoc = System.Xml.Linq.XDocument.Parse(text, LoadOptions.None);
            SqlLockData data = new SqlLockData(eventInfo);
			XElement root = xDoc.Root;
			if (root.Name.LocalName != SqlConstants.DeadlockRootElement) return null;

            // Hlavička:
			XElement deadlock = root.Element(SqlConstants.DeadlockDataElement);
            if (deadlock == null) return null;

            data.Victim = GetAttributeValue(deadlock.Attribute(SqlConstants.VictimAttribute));

            // Procesy:
            XElement processList = deadlock.Element(SqlConstants.ProcessListElement);
            if (processList == null) return null;
            foreach (XElement process in processList.Elements(SqlConstants.ProcessElement))
            {
                // Process ID:
                SqlProcessInfo processInfo = SqlProcessInfo.CreateFromXml(process, data);
                if (processInfo != null)
                {
                    if (!data.Processes.ContainsKey(processInfo.Id))
                        data.Processes.Add(processInfo.Id, processInfo);
                    else
                        System.Windows.Forms.MessageBox.Show("Duplicitní klíč ProcessId: " + processInfo.ToString() + " !");
                }

                // SPID:
                string spid = (String.IsNullOrEmpty(processInfo.SpID) ? "" : processInfo.SpID);
                SqlSpidInfo spidInfo;
                if (!data.SpIds.TryGetValue(spid, out spidInfo))
                {
                    spidInfo = new SqlSpidInfo(spid, data);
                    data.SpIds.Add(spid, spidInfo);
                }
                if (!spidInfo.Processes.ContainsKey(processInfo.Id))
                    spidInfo.Processes.Add(processInfo.Id, processInfo);
                else
                    System.Windows.Forms.MessageBox.Show("Duplicitní klíč ProcessId: " + processInfo.ToString() + " v SPID: " + spid + " !");
            }

            // Zdroje:
            XElement resourceList = deadlock.Element(SqlConstants.ResourceListElement);
            if (resourceList == null) return null;
            foreach (XElement resource in resourceList.Elements())
            {   // resource mají různé názvy elementů, ale všechny mají shodnou strukturu, načítanou do objektu SqlResourceInfo
                SqlResourceInfo resourceInfo = SqlResourceInfo.CreateFromXml(resource, data);
                if (resourceInfo != null)
                {	// Pozor, v některých záznamech se může jeden resource vyskytovat ve více elementech!
					data.AddResourceToProcesses(resourceInfo);
					// Tyto různé výskyty mají shodné atributy, ale další výskyt přidává nové prvky participantů (owner a/nebo waiter):
                    if (!data.Resources.ContainsKey(resourceInfo.Id))
                        data.Resources.Add(resourceInfo.Id, resourceInfo);
                    else
						data.Resources[resourceInfo.Id].Add(resourceInfo);
                }
            }

			// Setřídění dat:
			data.FinalizeData();

			// Určit datum deadlocku:
			if (deadlockTime.HasValue)
				data.DeadlockTime = deadlockTime;
			else
				data.DeadlockTime = data.MaxTime;
			return data;
		}
		/// <summary>
		/// Přidá vazby na zdroj (parametr) do procesů, k nimž se zdroj vztahuje
		/// </summary>
		/// <param name="resourceInfo"></param>
		private void AddResourceToProcesses(SqlResourceInfo resourceInfo)
		{
			foreach (SqlParticipantInfo participantInfo in resourceInfo.OwnerList)
			{
				SqlProcessInfo processInfo = participantInfo.ProcessInfo;
				if (processInfo != null)
					processInfo.ResourcesOwned.Add(resourceInfo);
			}
			foreach (SqlParticipantInfo participantInfo in resourceInfo.WaiterList)
			{
				SqlProcessInfo processInfo = participantInfo.ProcessInfo;
				if (processInfo != null)
					processInfo.ResourcesWaited.Add(resourceInfo);
			}
		}
		/// <summary>
		/// Setřídění dat po jejich kompletním načtení
		/// </summary>
		private void FinalizeData()
		{
			// Setříděný seznam SPID:
			this.SpIdList = new List<SqlSpidInfo>(this.SpIds.Values);
			this.SpIdList.Sort(SqlSpidInfo.CompareByTransBegin);

			// Přenést pořadí SPID do Resources:
			foreach (SqlResourceInfo resourceInfo in this.Resources.Values)
				resourceInfo.SpidOrder = null;
			for (int o = 0; o < this.SpIdList.Count; o++)
				this.SpIdList[o].SetSpidOrder(o);

			// Setříděný seznam Resources:
			this.ResourceList = new List<SqlResourceInfo>(this.Resources.Values);
			this.ResourceList.Sort(SqlResourceInfo.CompareByOwnerOrder);
		}
		/// <summary>
		/// Určí nejvyšší čas ze všech svých objektů (z procesů).
		/// </summary>
		/// <returns></returns>
		public DateTime? MaxTime
		{
			get
			{
				DateTime? maxTime = null;
				foreach (SqlProcessInfo processInfo in this.Processes.Values)
				{
					maxTime = SqlProcessInfo.GetMaxTime(maxTime, processInfo.MaxTime);
				}
				return maxTime;
			}
		}
        /// <summary>
        /// Vrátí hodnotu atributu.
        /// Nic moc metoda---
        /// </summary>
        /// <param name="attribute"></param>
        /// <returns></returns>
        protected static string GetAttributeValue(XAttribute attribute)
        {
            if (attribute == null) return "";
            return attribute.Value;
        }
        /// <summary>
        /// Najde a vrátí proces daného ID
        /// </summary>
        /// <param name="processId"></param>
        /// <returns></returns>
        internal SqlProcessInfo GetProcess(string processId)
        {
            SqlProcessInfo processInfo;
            if (this.Processes.TryGetValue(processId, out processInfo))
                return processInfo;
            return null;
        }
        /// <summary>
        /// Vrátí data SPID pro daný klíč SPID
        /// </summary>
        /// <param name="spid"></param>
        /// <returns></returns>
        internal SqlSpidInfo GetSpid(string spid)
        {
            SqlSpidInfo spidInfo;
            if (this.SpIds.TryGetValue(spid, out spidInfo)) return spidInfo;
            return null;
        }
        /// <summary>
        /// Vrátí data SPID pro daný klíč ProcessId
        /// </summary>
        /// <param name="processId"></param>
        /// <returns></returns>
        internal SqlSpidInfo GetSpidForProcessId(string processId)
        {
            SqlProcessInfo processInfo = GetProcess(processId);
            if (processInfo == null) return null;
            return GetSpid(processInfo.SpID);
        }
        #endregion
        #region Property
        /// <summary>
        /// Klíč procesu, který je deadlockem ukončen (Oběť)
        /// </summary>
        public string Victim { get; private set; }
		/// <summary>
		///Okamžik deadlocku
		/// </summary>
		public DateTime? DeadlockTime { get; private set; }
		/// <summary>
		/// Sada informací o vláknech SPID v době deadlocku.
        /// Klíčem je SPID vlákna.
        /// Hodnotou je objekt SqlSpidInfo, který v sobě zahrnuje více procesů.
		/// </summary>
        public Dictionary<string, SqlSpidInfo> SpIds { get; private set; }
		/// <summary>
		/// Sekundární seznam SPID vláken, setříděný podle okamžiku zahájení transakce a poté podle SPID.
		/// </summary>
		public List<SqlSpidInfo> SpIdList { get; private set; }
		/// <summary>
		/// Sada informací o procesech v době deadlocku.
        /// Klíčem je ID = process.
		/// </summary>
        public Dictionary<string, SqlProcessInfo> Processes { get; private set; }
		/// <summary>
		/// Sada informací o zdrojích a jejich vztazích v době deadlocku
		/// </summary>
        public Dictionary<string, SqlResourceInfo> Resources { get; private set; }
		/// <summary>
		/// Sekundární seznam SqlResourceInfo, setříděný podle pořadí vlastníka SPID
		/// </summary>
		public List<SqlResourceInfo> ResourceList { get; private set; }
		#endregion
        #region Podpora pro konverzi datumů ze stringu do DateTime?
        /// <summary>
        /// Konevrtuje datum a čas z textu SQL trace na DateTime?
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
		internal static DateTime? ParseUtcDateTime(string text)
        {
            DateTime? result = null;
            int at = text.IndexOf("+");
            if (at > 0)
                text = text.Substring(0, at);
            if (text.Length > 23)
                text = text.Substring(0, 23);
			if (text.Length >= 19)
			{
				if (text.Length == 19)             // Délka 19 = "yyyy-MM-ddTHH:mm:ss"
					text += ".000";                //             12345678901234567890123456789
				else if (text.Length < 23)         // Délka 24 = "yyyy-MM-ddTHH:mm:ss.fff"
					text = text.PadRight(23, '0'); //             12345678901234567890123456789
				DateTime pars;
				if (DateTime.TryParseExact(text, "F", DtFi, System.Globalization.DateTimeStyles.NoCurrentDateDefault | System.Globalization.DateTimeStyles.AllowWhiteSpaces, out pars))
					result = pars;
			}
            return result;
        }
        /// <summary>
		/// FormatInfo pro konverzi SQL datetime do .NET (pro formát "F", hodnota FullDateTimePattern = "yyyy-MM-dd'T'HH:mm:ss.fff")
        /// </summary>
		internal static System.Globalization.DateTimeFormatInfo DtFi
        {
            get
            {
                if (__Dtfi == null)
                {
                    System.Globalization.DateTimeFormatInfo dfi = new System.Globalization.DateTimeFormatInfo();
                    dfi.FullDateTimePattern = "yyyy-MM-dd'T'HH:mm:ss.fff";
                    __Dtfi = dfi;
                }
                return __Dtfi;
            }
        }
		private static System.Globalization.DateTimeFormatInfo __Dtfi;
		internal static string GetIntNAsIndex(Int32? value)
		{
			if (value.HasValue)
				return "[" + value.Value.ToString() + "]";
			return "[?]";
		}
        #endregion
    }
    #endregion
    #region class SqlSpidInfo : data o jednom SPID = obálka na více objektů třídy SqlProcessInfo
    /// <summary>
    /// SqlSpidInfo : data o jednom SPID = obálka na více objektů třídy SqlProcessInfo.
    /// Objekt sám není načítán z XML dat, ale vzniká jako container pro více SqlProcessInfo shodného SPID.
    /// </summary>
    internal class SqlSpidInfo
    {
        #region Konstrukce
        public SqlSpidInfo(string spid, SqlLockData parent)
        {
            this.Parent = parent;
            this.SpID = spid;
            this.SpIdNumber = 0;
            this.Processes = new Dictionary<string, SqlProcessInfo>();

            int spidNumber;
            if (spid != null && Int32.TryParse(spid.TrimStart(' ', '0'), out spidNumber))
                this.SpIdNumber = spidNumber;
        }
		public override string ToString()
		{
			return SqlLockData.GetIntNAsIndex(this.SpidOrder) + " SPID: " + this.SpID + "; ProcessCount=" + this.Processes.Count.ToString();
		}
        #endregion
        #region Property
        /// <summary>
        /// Parent tohoto objektu = data deadlocku
        /// </summary>
        public SqlLockData Parent { get; protected set; }
        /// <summary>
        /// SPID procesu, text opsaný z trace
        /// </summary>
        public string SpID { get; protected set; }
        /// <summary>
        /// SPID procesu, číslo
        /// </summary>
        public int SpIdNumber { get; protected set; }
		/// <summary>
		/// true, pokud v tomto SPID je některý proces obětí deadlocku
		/// </summary>
		public bool IsVictim { get { return (this.Processes.Any(p => p.Value.IsVictim)); } }
		/// <summary>
		/// Pořadí po setřídění (komparátorem CompareByTransBegin), přenese se do OwnedResources a pomůže setřídit zdroje
		/// </summary>
		public Int32? SpidOrder { get; set; }
		/// <summary>
        /// Soupis procesů
        /// </summary>
        public Dictionary<string, SqlProcessInfo> Processes { get; private set; }
        /// <summary>
        /// Obsahuje nejnižší datum zahájení transakce, anebo null pokud v tomto SPID nebyla zahájena
        /// </summary>
        public DateTime? LastTransactionStarted
        {
            get
            {
                DateTime? minDate = null;
                foreach (SqlProcessInfo processInfo in Processes.Values)
                {
                    DateTime? prcDate = processInfo.LastTransactionStarted;
                    if (prcDate.HasValue && (!minDate.HasValue || (minDate.HasValue && minDate.Value > prcDate.Value)))
                        minDate = prcDate;
                }
                return minDate;
            }
        }
		/// <summary>
		/// true, pokud tento SPID (některý z jeho procesů) vlastní nějaký zdroj
		/// </summary>
		public bool IsOwnerOfAnyResource { get { return this.Processes.Any(p => p.Value.IsOwnerOfAnyResource); } }
		/// <summary>
		/// true, pokud tento SPID (některý z jeho procesů) čeká na nějaký zdroj
		/// </summary>
		public bool IsWaiterToAnyResource { get { return this.Processes.Any(p => p.Value.IsWaiterToAnyResource); } }
		/// <summary>
		/// Nastaví do sebe a do našich vlastněných resources dané pořadí
		/// </summary>
		/// <param name="order"></param>
		internal void SetSpidOrder(int order)
		{
			this.SpidOrder = order;
			foreach (SqlProcessInfo processInfo in this.Processes.Values)
				processInfo.SetSpidOrder(order);
		}
		#endregion
		#region Komparátory
		/// <summary>
        /// Zajistí třídění podle data transakce: nejstarší (nejnižší hodnota LastTransactionStarted) bude první.
        /// Položky kde LastTransactionStarted je null budou na konci, tříděné dle SPID.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static int CompareByTransBegin(SqlSpidInfo a, SqlSpidInfo b)
        {
            if (a == null && b == null) return 0;
            if (a == null) return -1;
            if (b == null) return 1;

            DateTime? at = a.LastTransactionStarted;
            DateTime? bt = b.LastTransactionStarted;
            if (at.HasValue && bt.HasValue) return at.Value.CompareTo(bt.Value);        // Nižší datum dříve
            if (at.HasValue) return -1;                   // Položky s hodnotou budou před položkami bez hodnoty
            if (bt.HasValue) return 1;

            return a.SpIdNumber.CompareTo(b.SpIdNumber);  // Obě položky bez data: třídím dle SPID
        }
        #endregion
	}
    #endregion
    #region class SqlProcessInfo : data o jednom procesu v blokování
    /// <summary>
    /// Data o jednom SQL procesu
    /// </summary>
    internal class SqlProcessInfo : SqlInfo
    {
        #region Konstrukce a načtení z XML
        protected SqlProcessInfo(XElement process, SqlLockData parent)
            : base(process, SqlConstants.IdAttribute)
        {
            // base konstruktor načte Id a všechny atributy.

            // Vytvoříme svoje objekty:
            this.Parent = parent;
            this.ExecutionStackList = new List<SqlInfo>();
			this.ResourcesOwned = new List<SqlResourceInfo>();
			this.ResourcesWaited = new List<SqlResourceInfo>();

            // Nyní do specifických property načteme hodnoty z daných atributů:
            this.SpID = this.GetAttributeValue(SqlConstants.ProcessSpID);
            this.WaitResource = this.GetAttributeValue(SqlConstants.ProcessWaitResource);
            this.LastTransactionStarted = this.GetAttributeDateTimeN(SqlConstants.ProcessLastTransactionStared);
            this.LockMode = this.GetAttributeValue(SqlConstants.ProcessLockMode);
            this.Status = this.GetAttributeValue(SqlConstants.ProcessStatus);
            this.TranCount = this.GetAttributeValue(SqlConstants.ProcessTranCount);
            this.LastBatchStarted = this.GetAttributeDateTimeN(SqlConstants.ProcessLastBatchStarted);
            this.LastBatchCompleted = this.GetAttributeDateTimeN(SqlConstants.ProcessLastBatchCompleted);

            // Zpracujeme interní elementy:
            foreach (XElement element in process.Elements())
            {
                string elementName = element.Name.LocalName;
                switch (elementName)
                {
                    case SqlConstants.ProcessExecutionStackElement:
                        this._LoadExecutionStack(element);
                        break;
                    case SqlConstants.ProcessInputBufElement:
                        this._LoadInputBuf(element);
                        break;
                    default:
                        System.Windows.Forms.MessageBox.Show("Neznámý element <process> <" + elementName + "> !");
                        break;
                }
            }
        }
        /// <summary>
        /// Načte obsah elementu (executionStack)
        /// </summary>
        /// <param name="element"></param>
        private void _LoadExecutionStack(XElement element)
        {
            foreach (XElement subElement in element.Elements())
            {   // Očekávám elementy jména <frame>, obsahují data o provedených SQL příkazech:
                string elementName = subElement.Name.LocalName;
                switch (elementName)
                {
                    case SqlConstants.ExecutionStackFrameElement:
                        SqlInfo frame = SqlInfo.CreateFromXml(subElement);
                        this.ExecutionStackList.Add(frame);
                        break;
                    default:
                        System.Windows.Forms.MessageBox.Show("Neznámý element <process> <executionStack> <" + elementName + "> !");
                        break;
                }
            }
        }
        /// <summary>
        /// Načte obsah elementu (inputbuf)
        /// </summary>
        /// <param name="element"></param>
        private void _LoadInputBuf(XElement element)
        {
            this.InputBuffer = element.Value.Trim(' ', '\t', '\r', '\n');
        }
        /// <summary>
        /// Načte a vrátí objekt s daty procesu (kompletní objekt)
        /// </summary>
        /// <param name="process"></param>
        /// <param name="parent"></param>
        /// <returns></returns>
        public static SqlProcessInfo CreateFromXml(XElement process, SqlLockData parent)
        {
            SqlProcessInfo processInfo = new SqlProcessInfo(process, parent);
            return processInfo;
        }
        public override string ToString()
        {
            return "SpID=" + this.SpID + "; ProcessID=" + this.Id;
        }
        #endregion
        #region Property
        /// <summary>
        /// Parent tohoto objektu = data deadlocku
        /// </summary>
        public SqlLockData Parent { get; protected set; }
        public string SpID { get; protected set; }
        public string WaitResource { get; protected set; }
        public DateTime? LastTransactionStarted { get; protected set; }
        public string LockMode { get; protected set; }
        public string Status { get; protected set; }
        public string TranCount { get; protected set; }
        public DateTime? LastBatchStarted { get; protected set; }
        public DateTime? LastBatchCompleted { get; protected set; }
        public List<SqlInfo> ExecutionStackList { get; protected set; }
        public string InputBuffer { get; protected set; }
		/// <summary>
		/// true, pokud tento proces osobně je obětí deadlocku
		/// </summary>
		public bool IsVictim { get { return (this.Id == this.Parent.Victim); } }
		/// <summary>
		/// true, pokud tento proces vlastní nějaký zdroj
		/// </summary>
		public bool IsOwnerOfAnyResource { get { return this.ResourcesOwned.Count > 0; } }
		/// <summary>
		/// true, pokud tento proces čeká na nějaký zdroj
		/// </summary>
		public bool IsWaiterToAnyResource { get { return this.ResourcesWaited.Count > 0; } }
		/// <summary>
		/// Zdroje, které vlastníme
		/// </summary>
		public List<SqlResourceInfo> ResourcesOwned { get; protected set; }
		/// <summary>
		/// Zdroje, na které čekáme
		/// </summary>
		public List<SqlResourceInfo> ResourcesWaited { get; protected set; }
		/// <summary>
		/// Pořadí po setřídění (komparátorem CompareByTransBegin), přenese se do OwnedResources a pomůže setřídit zdroje
		/// </summary>
		public Int32? SpidOrder { get; set; }
		/// <summary>
		/// Určí nejvyšší čas ze všech svých datumů.
		/// </summary>
		/// <returns></returns>
		public DateTime? MaxTime
		{
			get
			{
				DateTime? maxTime = GetMaxTime(this.LastTransactionStarted, this.LastBatchStarted, this.LastBatchCompleted);
				return maxTime;
			}
		}
		/// <summary>
		/// Vrátí nejvyšší hodnotu ze zadaných nenulových hodnot.
		/// </summary>
		/// <param name="values"></param>
		/// <returns></returns>
		internal static DateTime? GetMaxTime(params DateTime?[] values)
		{
			DateTime? maxTime = null;
			foreach (DateTime? value in values)
			{
				if (value.HasValue && (!maxTime.HasValue || (maxTime.HasValue && value.Value > maxTime.Value)))
					maxTime = value;
			}
			return maxTime;
		}
		/// <summary>
		/// Nastaví do sebe a do našich vlastněných resources dané pořadí
		/// </summary>
		/// <param name="order"></param>
		internal void SetSpidOrder(int order)
		{
			this.SpidOrder = order;
			foreach (SqlResourceInfo resourceInfo in this.ResourcesOwned)
			{
				if (!resourceInfo.SpidOrder.HasValue)
					resourceInfo.SpidOrder = order;
			}
		}
		#endregion
    }
    #endregion
    #region class SqlResourceInfo : data o jednom zdroji, o který se vedou boje
    /// <summary>
    /// Data o jednom zdroji, o který se vedou boje
    /// </summary>
    internal class SqlResourceInfo : SqlInfo
    {
        #region Konstrukce a načtení z XML
        protected SqlResourceInfo(XElement resource, SqlLockData parent)
            : base(resource, SqlConstants.IdAttribute)
        {
            // base konstruktor načte Id a všechny atributy.

            // Vytvoříme svoje objekty:
            this.Parent = parent;
            this.OwnerList = new List<SqlParticipantInfo>();
            this.WaiterList = new List<SqlParticipantInfo>();

            // Nyní do specifických property načteme hodnoty z daných atributů:
            this.ObjectName = this.GetAttributeValue(SqlConstants.ResourceObjectName);
            this.Mode = this.GetAttributeValue(SqlConstants.ResourceMode);

            // Zpracujeme interní elementy (zde očekávám elementy <owner-list> a <waiter-list>):
            foreach (XElement element in resource.Elements())
            {
                string elementName = element.Name.LocalName;
                switch (elementName)
                {
                    case SqlConstants.OwnerListElement:
                        this._LoadParticipantList(element, SqlConstants.OwnerElement);
                        break;
                    case SqlConstants.WaiterListElement:
                        this._LoadParticipantList(element, SqlConstants.WaiterElement);
                        break;
                    default:
                        System.Windows.Forms.MessageBox.Show("Neznámý element <resource-list> <" + this.Name + "> <" + elementName + "> !");
                        break;
                }
            }
        }
        /// <summary>
        /// Načte a vrátí objekt s daty zdroje (kompletní objekt)
        /// </summary>
        /// <param name="resource"></param>
        /// <param name="parent"></param>
        /// <returns></returns>
        public static SqlResourceInfo CreateFromXml(XElement resource, SqlLockData parent)
        {
            SqlResourceInfo resourceInfo = new SqlResourceInfo(resource, parent);
            return resourceInfo;
        }
        /// <summary>
        /// Načte členy týmu vlastníků (owner-list) nebo členy týmu čekatelů (waiter-list)
        /// </summary>
        /// <param name="element"></param>
        private void _LoadParticipantList(XElement element, string estimatedElement)
        {
            SqlParticipantInfo participantInfo;
            foreach (XElement subElement in element.Elements())
            {
                string elementName = subElement.Name.LocalName;
                if (elementName != estimatedElement)
                {
                    System.Windows.Forms.MessageBox.Show("Nečekaný element <resource-list>: očekávaný element = <" + estimatedElement + ">, aktuální element = <" + elementName + "> !");
                }
                else
                {
                    switch (elementName)
                    {
                        case SqlConstants.OwnerElement:
                            participantInfo = SqlParticipantInfo.CreateFromXml(subElement, this.Parent);
                            if (participantInfo != null)
                                this.OwnerList.Add(participantInfo);
                            break;
                        case SqlConstants.WaiterElement:
                            participantInfo = SqlParticipantInfo.CreateFromXml(subElement, this.Parent);
                            if (participantInfo != null)
                                this.WaiterList.Add(participantInfo);
                            break;
                        default:
                            System.Windows.Forms.MessageBox.Show("Neznámý element <resource-list> <" + element.Name.LocalName + "> <" + elementName + "> !");
                            break;
                    }
                }
            }
        }
		/// <summary>
		/// Do this prvku přidá elementy Owner a Waiter z dodaného objektu.
		/// Používá se tehdy, když deklarace blokování obsahuje jeden resource ve více výskytech.
		/// </summary>
		/// <param name="resourceInfo"></param>
		internal void Add(SqlResourceInfo resourceInfo)
		{
			this.OwnerList.AddRange(resourceInfo.OwnerList);
			this.WaiterList.AddRange(resourceInfo.WaiterList);
		}
		public override string ToString()
        {
            string text = SqlLockData.GetIntNAsIndex(this.SpidOrder) + " Resource=" + this.Name;
            if (String.IsNullOrEmpty(this.ObjectName))
                text += "; Id=" + this.Id;
            else
                text += "; ObjectName=" + this.ObjectName;
            return text;
        }
        #endregion
        #region Property
        /// <summary>
        /// Parent tohoto objektu = data deadlocku
        /// </summary>
        protected SqlLockData Parent { get; private set; }
        /// <summary>
        /// Typ resource = pagelock, keylock, exchangeEvent
        /// </summary>
        public string ResourceType { get { return this.Name; } }
        /// <summary>
        /// Název objektu, nemusí být naplněn vždy
        /// </summary>
        public string ObjectName { get; private set; }
        /// <summary>
        /// Mod zámku
        /// </summary>
        public string Mode { get; private set; }
		/// <summary>
		/// Pořadí po setřídění SPID (komparátorem CompareByTransBegin), přenese se do OwnedResources a pomůže setřídit zdroje
		/// </summary>
		public Int32? SpidOrder
		{
			get { return _SpidOrder; }
			set
			{
				if (this._SpidOrder.HasValue && !value.HasValue)
				{
				}
				this._SpidOrder = value;
			} 
		}
		private Int32? _SpidOrder;
		/// <summary>
        /// Seznam vlastníků zdroje
        /// </summary>
        public List<SqlParticipantInfo> OwnerList { get; private set; }
        /// <summary>
        /// Seznam čekatelů na zdroj
        /// </summary>
        public List<SqlParticipantInfo> WaiterList { get; private set; }
        #endregion
        #region Seznamy Owners a Waiters (nikoli SqlParticipantInfo, ale SqlSpidInfo)
        /// <summary>
        /// Seznam vlastníků tohoto objektu ve formě seznamu SqlSpidInfo
        /// </summary>
        public List<RelationSpidResource> Owners { get { return GetSpidList(this.OwnerList); } }
        /// <summary>
        /// Seznam vlastníků tohoto objektu ve formě seznamu SqlSpidInfo
        /// </summary>
        public List<RelationSpidResource> Waiters { get { return GetSpidList(this.WaiterList); } }
        /// <summary>
        /// Vrátí seznam dat SqlSpidInfo pro daný seznam participantů
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        private List<RelationSpidResource> GetSpidList(List<SqlParticipantInfo> list)
        {
            Dictionary<string, RelationSpidResource> dict = new Dictionary<string, RelationSpidResource>();
            foreach (SqlParticipantInfo participant in list)
            {
                SqlSpidInfo spidInfo = participant.SpidInfo;
                if (spidInfo != null)
                {
                    RelationSpidResource rel;
                    if (dict.TryGetValue(spidInfo.SpID, out rel))
                        rel.AddParticipant(participant);
                    else
                        dict.Add(spidInfo.SpID, new RelationSpidResource(this, participant));
                }
            }
            return new List<RelationSpidResource>(dict.Values);
        }
        #endregion
		#region Komparátory
		public static int CompareByOwnerOrder(SqlResourceInfo a, SqlResourceInfo b)
		{
			if (a == null && b == null) return 0;
			if (a == null) return -1;
			if (b == null) return 1;

			Int32? ao = a.SpidOrder;
			Int32? bo = b.SpidOrder;
			if (ao.HasValue && bo.HasValue) return ao.Value.CompareTo(bo.Value);        // Nižší pořadí dříve
			if (ao.HasValue) return -1;                   // Položky s hodnotou budou před položkami bez hodnoty
			if (bo.HasValue) return 1;

			return String.Compare(a.Id, b.Id);
		}
		#endregion
	}
    #endregion
    #region class RelationSpidResource : třída popisující obsahující vztah SPID a Resource včetně textů
    /// <summary>
    /// RelationSpidResource : třída popisující obsahující vztah SPID a Resource včetně textů.
    /// Čistě datová třída, bez vizuálních prvků.
    /// </summary>
    internal class RelationSpidResource 
    {
        public RelationSpidResource (SqlResourceInfo resource, SqlParticipantInfo participant)
        {
            this.ResourceInfo = resource;
            this.Participant = participant;
            this.Mode = null;
            this.RequestType = null;
            this.AddParticipant(participant);
        }

        public void AddParticipant(SqlParticipantInfo participant)
        {
            if (participant.SpidInfo.SpID != this.Participant.SpidInfo.SpID)
                throw new InvalidOperationException("Nelze slučovat záznamy SqlParticipantInfo do záznamu RelationSpidResource pro různé SPID (current=" + this.Participant.SpidInfo.SpID + "; Add=" + participant.SpidInfo.SpID + ")");

            this.Mode = (this.Mode != null ? this.Mode + Environment.NewLine : "") + participant.Mode;
            this.RequestType = (this.RequestType != null ? this.RequestType + Environment.NewLine : "") + participant.RequestType;
        }
        public SqlResourceInfo ResourceInfo { get; private set; }
        public SqlParticipantInfo Participant { get; private set; }
        public SqlSpidInfo SpidInfo { get { return this.Participant.SpidInfo; } }
        public SqlProcessInfo ProcessInfo { get { return this.Participant.ProcessInfo; } }
        public string Mode { get; private set; }
        public string RequestType { get; private set; }
        /// <summary>
        /// Obsahuje textovou kombinaci hodnot this.RequestType [: ] this.Mode
        /// </summary>
        public string ModeText
        {
            get
            {
                string result = "";
                if (!String.IsNullOrEmpty(this.RequestType))
                    result += this.RequestType;                              // "wait";
                if (!String.IsNullOrEmpty(this.Mode))
                    result += (result.Length == 0 ? "" : ": ") + this.Mode;  // "wait: SIX"
                return result;
            }
        }
    }
    #endregion
	#region class SqlParticipantInfo : účastník konfliktu o zdroj.
	/// <summary>
    /// SqlParticipantInfo : účastník konfliktu o zdroj. Může být buď vlastník zdroje (Owner), anebo čekatel (Waiter).
    /// </summary>
    internal class SqlParticipantInfo : SqlInfo
    {
        #region Konstrukce
        protected SqlParticipantInfo(XElement participant, SqlLockData parent)
            : base(participant, null)
        {
            this.Parent = parent;
            this.ProcessId = GetAttributeValue(SqlConstants.IdAttribute);           // ID v elementu = ID procesu
            this.Mode = GetAttributeValue(SqlConstants.ParticipantMode);
            this.RequestType = GetAttributeValue(SqlConstants.ParticipantRequestType);
        }
        /// <summary>
        /// Načte a vrátí objekt s daty účastníka konfliktu o zdroj (kompletní objekt)
        /// </summary>
        /// <param name="participant"></param>
        /// <param name="parent"></param>
        /// <returns></returns>
        public static SqlParticipantInfo CreateFromXml(XElement participant, SqlLockData parent)
        {
            SqlParticipantInfo participantInfo = new SqlParticipantInfo(participant, parent);
            return participantInfo;
        }
        public override string ToString()
        {
            SqlProcessInfo process = this.ProcessInfo;
            return this.Name + ": " + (process == null ? this.ProcessId : process.ToString()) + "; Mode=" + this.Mode;
        }
        #endregion
        #region Property
        /// <summary>
        /// ID procesu
        /// </summary>
        public string ProcessId { get; private set; }
        /// <summary>
        /// Mod zámku (vlastněného nebo požadovaného)
        /// </summary>
        public string Mode { get; private set; }
        /// <summary>
        /// Typ požadavku
        /// </summary>
        public string RequestType { get; private set; }
        /// <summary>
        /// Data procesu
        /// </summary>
        public SqlProcessInfo ProcessInfo { get { return this.Parent.GetProcess(this.ProcessId); } }
        /// <summary>
        /// Data SPID
        /// </summary>
        public SqlSpidInfo SpidInfo { get { return this.Parent.GetSpidForProcessId(this.ProcessId); } }
        /// <summary>
        /// Parent data grafu
        /// </summary>
        protected SqlLockData Parent { get; private set; }
        #endregion
    }
    #endregion
    #region class SqlInfo : bázová třída pro data, která se načítají z XML
    /// <summary>
    /// Data o jedné entitě na SQL serveru. Obecná třída, předek všech dalších SQL datových dat.
    /// </summary>
    internal class SqlInfo
    {
        #region Konstrukce
        /// <summary>
        /// Vytvoří a vrátí instanci vytvořenou z XElementu.
        /// Načte všechny atributy a načte Value.
        /// Vnitřní elementy neřeší, od toho jsou tu potomkové.
        /// </summary>
        /// <param name="element"></param>
        protected SqlInfo(XElement element, string idName)
        {
            this.Name = element.Name.LocalName;
            this.LoadAttributes(element);
            this.Id = (String.IsNullOrEmpty(idName) ? "" : this.GetAttributeValue(idName));
            string value = element.Value;
            if (String.IsNullOrEmpty(value))
                this.Value = "";
            else
                this.Value = value.Trim(' ', '\t', '\r', '\n');
        }
        /// <summary>
        /// Vytvoří a vrátí instanci vytvořenou z XElementu.
        /// Načte všechny atributy a načte Value.
        /// Vnitřní elementy neřeší, od toho jsou tu potomkové.
        /// </summary>
        /// <param name="element"></param>
        public static SqlInfo CreateFromXml(XElement element)
        {
            SqlInfo info = new SqlInfo(element, null);
            return info;
        }
        /// <summary>
        /// Vytvoří a vrátí instanci vytvořenou z XElementu.
        /// Načte všechny atributy a načte Value.
        /// Vnitřní elementy neřeší, od toho jsou tu potomkové.
        /// </summary>
        /// <param name="idName"></param>
        /// <param name="element"></param>
        public static SqlInfo CreateFromXml(XElement element, string idName)
        {
            SqlInfo info = new SqlInfo(element, idName);
            return info;
        }
        /// <summary>
        /// Načte do sebe všechny atributy a jejich hodnoty pro daný element.
        /// </summary>
        /// <param name="element"></param>
        protected void LoadAttributes(XElement element)
        {
            this.AttributeList = new List<SqlNameValue>();
            foreach (XAttribute attribute in element.Attributes())
                this.AttributeList.Add(new SqlNameValue(attribute));
        }
        public override string ToString()
        {
            return this.Name + ": " + this.Value;
        }
        #endregion
        #region Property
        /// <summary>
        /// Name elementu
        /// </summary>
        public string Name { get; private set; }
        /// <summary>
        /// ID prvku
        /// </summary>
        public string Id { get; private set; }
        /// <summary>
        /// Value elementu = text uvedený v těle elementu
        /// </summary>
        public string Value { get; private set; }
        /// <summary>
        /// Seznam všech atributů
        /// </summary>
        public List<SqlNameValue> AttributeList { get; private set; }
        /// <summary>
        /// Obsahuje true, pokud je objekt korektně načten (má data ve všech patřičných property).
        /// Bázová třída SqlInfo kontroluje pouze hodnotu Id.
        /// </summary>
        public virtual bool IsCorrect { get { return !String.IsNullOrEmpty(this.Id); } }
        #endregion
        #region Sada atributů
        /// <summary>
        /// Najde a vrátí hodnotu atributu daného jména.
        /// Pokud nenajde, vrátí prázdný string.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public string GetAttributeValue(string name)
        {
 	        SqlNameValue attr = this.AttributeList.FirstOrDefault(i => i.Name == name);
            if (attr == null || String.IsNullOrEmpty(attr.Value)) return "";
            return attr.Value;
        }
        /// <summary>
        /// Najde a vrátí hodnotu atributu daného jména, konvrtovanou na DateTime?.
        /// Pokud nenajde, vrátí null.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public DateTime? GetAttributeDateTimeN(string name)
        {
            string value = GetAttributeValue(name);
            if (String.IsNullOrEmpty(value))
                return null;
            return SqlLockData.ParseUtcDateTime(value);
        }
        #endregion
    }
    #endregion
    #region class SqlNameValue : Uložení jednoho atributu (název + hodnota)
    /// <summary>
    /// SqlNameValue : Uložení jednoho atributu (název + hodnota)
    /// </summary>
    internal class SqlNameValue
    {
        public SqlNameValue(XAttribute attribute)
        {
            this.Name = attribute.Name.LocalName;
            this.Namespace = attribute.Name.NamespaceName;
            this.Value = attribute.Value;
        }
        public override string ToString()
        {
            return this.Name + " = " + this.Value;
        }
        /// <summary>
        /// Jméno atributu (lokální bez namespace)
        /// </summary>
        public string Name { get; private set; }
        /// <summary>
        /// Jméno namespace
        /// </summary>
        public string Namespace { get; private set; }
        /// <summary>
        /// Hodnota atributu
        /// </summary>
        public string Value { get; private set; }
    }
    #endregion
    #region class SqlConstants : konstanty
    internal class SqlConstants
    {
        public const string IdAttribute = "id";

        public const string DeadlockRootElement = "deadlock-list";
        public const string DeadlockDataElement = "deadlock";
        public const string VictimAttribute = "victim";

        public const string ProcessListElement = "process-list";
        public const string ProcessElement = "process";
        public const string ProcessWaitResource = "waitresource";
        public const string ProcessLastTransactionStared = "lasttranstarted";
        public const string ProcessLockMode = "lockMode";
        public const string ProcessStatus = "status";
        public const string ProcessSpID = "spid";
        public const string ProcessTranCount = "trancount";
        public const string ProcessLastBatchStarted = "lastbatchstarted";
        public const string ProcessLastBatchCompleted = "lastbatchcompleted";
        public const string ProcessExecutionStackElement = "executionStack";
        public const string ExecutionStackFrameElement = "frame";
        
        public const string ProcessInputBufElement = "inputbuf";

        
        

        public const string ResourceListElement = "resource-list";
        public const string ResourceObjectName = "objectname";
        public const string ResourceMode = "mode";
        public const string ResourceAssociatedObjectId = "associatedObjectId";

        public const string OwnerListElement = "owner-list";
        public const string OwnerElement = "owner";
        public const string WaiterListElement = "waiter-list";
        public const string WaiterElement = "waiter";
        public const string ParticipantMode = "mode";
        public const string ParticipantRequestType = "requestType";

        
        /*
        public const string Process.Attribute = "taskpriority";
        public const string Process.Attribute = "logused";
        public const string Process.Attribute = "waitresource";
        public const string Process.Attribute = "waittime";
        public const string Process.Attribute = "ownerId";
        public const string Process.Attribute = "transactionname";
        public const string Process.Attribute = "lasttranstarted";
        public const string Process.Attribute = "XDES";
        public const string Process.Attribute = "lockMode";
        public const string Process.Attribute = "schedulerid";
        public const string Process.Attribute = "kpid";
        public const string Process.Attribute = "status";
        public const string Process.Attribute = "spid";
        public const string Process.Attribute = "sbid";
        public const string Process.Attribute = "ecid";
        public const string Process.Attribute = "priority";
        public const string Process.Attribute = "trancount";
        public const string Process.Attribute = "lastbatchstarted";
        public const string Process.Attribute = "lastbatchcompleted";
        public const string Process.Attribute = "clientapp";
        public const string Process.Attribute = "hostname";
        public const string Process.Attribute = "hostpid";
        public const string Process.Attribute = "loginname";
        public const string Process.Attribute = "isolationlevel";
        public const string Process.Attribute = "xactid";
        public const string Process.Attribute = "currentdb";
        public const string Process.Attribute = "lockTimeout";
        public const string Process.Attribute = "clientoption1";
        public const string Process.Attribute = "clientoption2";
        */



    }
    #endregion
}
